﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Linq;
using System.IO;

namespace Erau.ERBParser
{
	/// <summary>
	/// 스크립트 파싱에 필요한 문자열 조작 함수들을 추가적으로 지원하는 클래스.
	/// Emuera의 StreamBuffer와 LexicalAnalyzer의 기능 일부를 합쳐놓았다고 생각하면 된다.
	/// 내부적으로 string을 쓴다. StringBuilder를 쓰지 않는 것은, 버퍼에 들어 있는 값을 파싱하거나 부분을 떼어낼 뿐 내부 문자열을 수정할 일이 거의 없기 때문.
	/// </summary>
	public class StringBuffer
	{
		public const char EndOfString = '\0';
		int pos;
		string buf;
		public string Buffer { get { return buf; } }

		public StringBuffer() : this(string.Empty) { }
		public StringBuffer(string s)
		{
			buf = s;
			if (buf == null) buf = string.Empty;
			pos = 0;
		}

		public static implicit operator StringBuffer(string v) { return new StringBuffer(v); }

		/// <summary>
		/// 현재 Cursor의 위치를 나타냄.
		/// </summary>
		public int Pos
		{
			get { return pos; }
			set { pos = value; }
		}

		public bool Empty { get { return string.IsNullOrEmpty(buf); } }
		#region 파싱 관련 함수들

		/// <summary>
		/// 현재 커서 위치에서 지정된 구분자가 있는 곳까지의 문자열을 반환하고, 커서를 해당 위치까지 전진시킨다.
		/// </summary>
		public string GetToken(char separator)
		{
			int orgPos = pos;
			while (Current != separator && !EOS) pos++;

			return Substring(orgPos, pos - orgPos);
		}

		/// <summary>
		/// 현재 커서 위치에서 지정된 구분자가 있는 곳까지의 문자열을 반환하고, 커서를 해당 위치까지 전진시킨다.
		/// </summary>
		/// <param name="separators">체크할 구분자의 목록</param>
		/// <returns></returns>
		public string GetToken(char[] separators)
		{
			int orgPos = pos;
			while (!separators.Contains(Current) && !EOS) pos++;

			return Substring(orgPos, pos - orgPos);
		}

		/// <summary>
		/// 현재 커서 위치에서부터 남은 문자열을 지정된 구분자로 분리한 후, 분리된 토큰의 목록을 반환한다.
		/// 이 함수는 남은 모든 문자열을 분리한 후, 커서를 맨 뒤로 이동시킨다.
		/// </summary>
		/// <param name="separators">체크할 구분자의 목록</param>
		/// <returns>구분자로 분리된 토큰의 리스트</returns>
		public string[] GetTokens(char[] separators)
		{
			this.Seek(0, SeekOrigin.End);
			return this.Substring().Split(separators);
		}

		/// <summary>
		/// 현재 커서 위치에서부터 남은 문자열을 지정된 구분자로 분리한 후, 분리된 토큰의 목록을 반환한다.
		/// 이 함수는 남은 모든 문자열을 분리한 후, 커서를 맨 뒤로 이동시킨다.
		/// </summary>
		/// <param name="separator">체크할 구분자</param>
		/// <returns>구분자로 분리된 토큰의 리스트</returns>
		public string[] GetTokens(char separator) {	return GetTokens(new char[] { separator }); }
		
		#endregion

		#region 기본 문자열 조작 함수들
		/// <summary>
		/// 현재 커서가 위치한 곳에 있는 문자를 반환.
		/// </summary>
		public char Current
		{
			get
			{
				if (pos >= buf.Length)
					return EndOfString;
				return buf[pos];
			}
		}
	
		/// <summary>
		/// End of String. 문자열의 끝에 도달했는지를 알려줌. 
		/// </summary>
		public bool EOS { get { return pos >= buf.Length; } }

		/// 바로 다음번 위치의 문자를 반환.
		public char Next
		{
			get
			{
				if (pos + 1 >= buf.Length)
					return EndOfString;
				return buf[pos + 1];
			}
		}

		/// <summary>
		/// 현재 커서가 위치한 곳에서부터 끝까지의 문자열을 반환한다.
		/// </summary>
		public string Substring()
		{
			if (pos >= buf.Length)
				return "";
			else if (pos == 0)
				return buf;
			return buf.Substring(pos);
		}

		/// <summary>
		/// 지정한 위치에서 지정된 길이만큼의 문자열을 반환한다.
		/// 위치와 길이가 유효한 값이 아닐 경우 언제나 String.Empty를 반환함이 보장된다.
		/// </summary>
		/// <param name="start"></param>
		/// <param name="length"></param>
		/// <returns></returns>
		public string Substring(int start, int length)
		{
			if (start >= buf.Length || length == 0)
				return string.Empty;
			if (start + length > buf.Length)
				length = buf.Length - start;
			return buf.Substring(start, length);
		}

		/// <summary>
		/// 지정된 범위의 문자열을 입력받은 문자열로 대체한다. 문자열 변경이나 추가는 퍼포먼스가 좋지 않다는 데 유의.
		/// </summary>
		internal void Replace(int start, int count, string src)
		{
			buf = (buf.Remove(start, count)).Insert(start, src);
			pos = start;
		}

		/// 커서를 앞으로 이동시킨다.
		public void ShiftNext()
		{
			pos++;
		}

		/// 커서를 지정된 값만큼 앞으로 이동시킨다.
        public void Jump(int skip)
        {
            pos += skip;
        }

		/// <summary>
		/// 현재 커서의 위치에서부터 지정된 문자열을 찾아서 위치를 반환.
		/// 반환되는 위치값이 현재 커서 위치로부터의 상태 위치라는 점에 유의할 것.
		/// </summary>
		/// <param name="str">검색할 문자열</param>
		/// <returns>현재 커서 위치(Pos)를 0으로 할 때 입력한 문자열이 있는 위치</returns>
		public int Find(string str)
		{
			return buf.IndexOf(str, pos) - pos;
		}

		/// <summary>
		/// 현재 커서의 위치에서부터 지정된 문자를 찾아서 위치를 반환.
		/// 반환되는 위치값이 현재 커서 위치로부터의 상태 위치라는 점에 유의할 것.
		/// </summary>
		/// <param name="c">검색할 문자</param>
		/// <returns>현재 커서 위치(Pos)를 0으로 할 때 입력한 문자가 있는 위치</returns>
		public int Find(char c)
		{
			return buf.IndexOf(c, pos) - pos;
		}

		public override string ToString()
		{
			if (buf == null)
				return "";
			return buf;
		}

		/// <summary>
		/// 현재 위치를 기준으로 문자열 동일 비교.
		/// </summary>
		public bool CurrentEqualTo(string rother)
		{
			if (pos + rother.Length > buf.Length)
				return false;

			for (int i = 0;  i < rother.Length;i++)
			{
				if (buf[pos + i] != rother[i])
					return false;
			}
			return true;
		}

		/// <summary>
		/// 현재 위치를 기준으로 문자열 동일 비교.
		/// </summary>
		public bool CurrentEqualTo(string rother, StringComparison comp)
		{
			if (pos + rother.Length > buf.Length)
				return false;
			string sub = buf.Substring(pos, rother.Length);
			return sub.Equals(rother, comp);
		}

		/// <summary>
		/// 현재 커서 위치로부터 3개의 연속된 같은 문자가 있는지 체크.
		/// </summary>
		public bool TripleSymbol()
		{
			if (pos + 3 > buf.Length)
				return false;
			return (buf[pos] == buf[pos + 1]) && (buf[pos] == buf[pos + 2]);
		}

		/// <summary>
		/// 지정된 위치로 커서 이동.
		/// </summary>
		public void Seek(int offset, SeekOrigin origin)
		{
			if (origin == SeekOrigin.Begin)
				pos = offset;
			else if (origin == SeekOrigin.Current)
				pos = pos + offset;
			else if (origin == SeekOrigin.End)
				pos = buf.Length + offset;
			if (pos < 0)
				pos = 0;
		}

		/// <summary>
		/// 문자열을 추가. StringBuffer 클래스는 이 함수가 자주 쓰이지 않는다는 전제하에 설계되었으므로, 반복작업시에는 StringBuilder 사용을 권장.
		/// </summary>
		/// <param name="str"></param>
		public void Append(string str)
		{
			if (pos > buf.Length)
				pos = buf.Length;
			buf += " " + str;
		}
		#endregion
	}
}
